﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Runtime.Caching;
using System.Text;

namespace Netstars.SessionCache
{
    #region 当缓存更新时的回调函数
    /// <summary>
    /// 当缓存更新时的回调函数
    /// </summary>
    /// <param name="key"></param>
    /// <param name="reason"></param>
    /// <param name="expensiveObject"></param>
    /// <param name="dependency"></param>
    public delegate void CacheItemUpdateCallback(string key, CacheItemRemovedReason reason, object expensiveObject, CacheDependency dependency); 
    #endregion

    #region 当缓存项移除时的回调函数
    /// <summary>
    /// 当缓存项移除时的回调函数
    /// </summary>
    /// <param name="key">移除缓存的键名</param>
    /// <param name="value">移除缓存的键值</param>
    /// <param name="reason">移除原因</param>
    public delegate void CacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason, CacheDependency dependency); 
    #endregion


    /// <summary>
    /// 私有会话缓存,可替代 Session 用于储存大对象
    /// </summary>
    public class SessionCache : IDisposable
    {
        #region 私有属性

        private readonly MemoryCache _cache;
        private static readonly object Lock = new object();
        private static readonly Dictionary<string, SessionCache> SessionCacheDictionary;
        private static readonly Dictionary<string, CacheItemUpdateCallback> OnUpdateCallbackDictionary;
        private static readonly Dictionary<string, CacheItemRemovedCallback> OnRemoveCallbackDictionary;
        private static readonly Dictionary<string, CacheDependency> CacheDependencies;

        #endregion

        #region 静态构造函数
        static SessionCache()
        {
            SessionCacheDictionary = new Dictionary<string, SessionCache>();
            OnUpdateCallbackDictionary = new Dictionary<string, CacheItemUpdateCallback>();
            OnRemoveCallbackDictionary = new Dictionary<string, CacheItemRemovedCallback>();
            CacheDependencies = new Dictionary<string, CacheDependency>();
        } 
        #endregion

        #region 私有构造函数
        /// <summary>
        /// 私有构造函数
        /// </summary>
        /// <param name="sessionId"></param>
        private SessionCache(string sessionId)
        {
            if (string.IsNullOrWhiteSpace(sessionId))
            {
                throw new ArgumentNullException("sessionId", "缓存名称不能为空!");
            }
            _cache = new MemoryCache(sessionId);
            Name = sessionId;
        } 
        #endregion

        #region 获取唯一 SessionCache 对象
        /// <summary>
        /// 获取唯一 SessionCache 对象
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public static SessionCache GetInstance(string sessionId)
        {
            if (string.IsNullOrWhiteSpace(sessionId))
            {
                throw new ArgumentNullException("sessionId", "缓存对象ID不能为空!");
            }
            SessionCache cache;
            if (SessionCacheDictionary.ContainsKey(sessionId))
            {
                SessionCacheDictionary.TryGetValue(sessionId, out cache);
            }
            else
            {
                lock (Lock)
                {
                    cache = new SessionCache(sessionId);
                    SessionCacheDictionary.Add(sessionId, cache);
                }
            }
            return cache;
        } 
        #endregion

        #region 静态属性
        /// <summary>
        /// 指示从不到期
        /// </summary>
        public static DateTime NoAbsoluteExpiration
        {
            get { return DateTime.MaxValue; }
        }

        /// <summary>
        /// 可调到期时间
        /// </summary>
        public static TimeSpan NoSlidingExpiration
        {
            get { return TimeSpan.Zero; }
        } 
        #endregion

        #region 公共属性
        /// <summary>
        /// 获取缓存的名称
        /// </summary>
        public string Name
        {
            private set;
            get;
        } 
        #endregion

        #region 插入缓存

        #region 添加一个缓存项
        /// <summary>
        /// 添加一个缓存项
        /// </summary>
        /// <param name="key">缓存的键名</param>
        /// <param name="value">缓存的键值</param>
        /// <returns></returns>
        public bool Insert(string key, object value)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "缓存的键值不能为空!");
            }
            if (_cache.Contains(key))
            {
                _cache.Remove(key);
            }
            return _cache.Add(new CacheItem(key, value), new System.Runtime.Caching.CacheItemPolicy());
        }
        #endregion

        #region 添加一个缓存项，并设置基于文件的缓存依赖
        /// <summary>
        /// 添加一个缓存项，并设置基于文件的缓存依赖
        /// </summary>
        /// <param name="key">缓存的键名</param>
        /// <param name="value">缓存的键值</param>
        /// <param name="dependencies">文件缓存依赖</param>
        /// <returns></returns>
        public bool Insert(string key, object value, CacheDependency dependencies)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "缓存的键值不能为空!");
            }
            if (_cache.Contains(key))
            {
                _cache.Remove(key);
            }
            System.Runtime.Caching.CacheItemPolicy policy = new System.Runtime.Caching.CacheItemPolicy();
            //文件依赖
            if (dependencies != null && dependencies.FilePath != null && dependencies.FilePath.Length > 0)
            {
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependencies.FilePath));
                if (CacheDependencies.ContainsKey(key))
                {
                    CacheDependencies.Remove(key);
                }
                CacheDependencies.Add(key, dependencies);
                return _cache.Add(new CacheItem(key, value), policy);
            }
            return _cache.Add(new CacheItem(key, value), null);
        }
        #endregion

        #region 添加一个缓存项，并设置基于文件的缓存依赖以及过期策略
        /// <summary>
        /// 添加一个缓存项，并设置基于文件的缓存依赖以及过期策略
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="dependencies"></param>
        /// <param name="absoluteExpiration"></param>
        /// <param name="slidingExpiration"></param>
        /// <returns></returns>
        public bool Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration,
            TimeSpan slidingExpiration)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "缓存的键值不能为空!");
            }
            if (absoluteExpiration == NoAbsoluteExpiration && slidingExpiration == NoSlidingExpiration)
            {
                throw new ArgumentException("absoluteExpiration和slidingExpiration不能全部为默认值!");
            }
            if (_cache.Contains(key))
            {
                Remove(key);
            }
            System.Runtime.Caching.CacheItemPolicy policy = new System.Runtime.Caching.CacheItemPolicy
            {
                AbsoluteExpiration = absoluteExpiration,
                SlidingExpiration = slidingExpiration
            };
            //文件依赖
            if (dependencies != null && dependencies.FilePath != null && dependencies.FilePath.Length > 0)
            {
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependencies.FilePath));
                if (CacheDependencies.ContainsKey(key))
                {
                    CacheDependencies.Remove(key);
                }
                CacheDependencies.Add(key, dependencies);
            }
            return _cache.Add(new CacheItem(key, value), policy);
        }
        #endregion

        #region 带有移除回调函数的插入缓存方法
        /// <summary>
        /// 带有移除回调函数的插入缓存方法
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="dependencies"></param>
        /// <param name="absoluteExpiration"></param>
        /// <param name="slidingExpiration"></param>
        /// <param name="priority"></param>
        /// <param name="onRemoveCallback"></param>
        /// <returns></returns>
        public bool Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration,
            TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "缓存的键值不能为空!");
            }
            if (dependencies == null && absoluteExpiration == NoAbsoluteExpiration && slidingExpiration == NoSlidingExpiration)
            {
                throw new ArgumentException("dependencies不能为空，并且absoluteExpiration和slidingExpiration不能全部为默认值!");
            }
            //添加回调函数
            if (onRemoveCallback == null)
            {
                throw new ArgumentNullException("onRemoveCallback", "缓存移除通知的委托不能为空!");
            }
            if (_cache.Contains(key))
            {
                Remove(key);
            }
            CacheItem item = new CacheItem(key, value);
            System.Runtime.Caching.CacheItemPolicy policy = new System.Runtime.Caching.CacheItemPolicy
            {
                AbsoluteExpiration = absoluteExpiration,
                RemovedCallback = RemovedCallback
            };
            //设置优先级
            switch (priority)
            {
                case CacheItemPriority.High:
                case CacheItemPriority.NotRemovable:
                    policy.Priority = System.Runtime.Caching.CacheItemPriority.NotRemovable;
                    break;
                default:
                    policy.Priority = System.Runtime.Caching.CacheItemPriority.Default;
                    break;
            }
            //文件依赖
            if (dependencies != null && dependencies.FilePath != null && dependencies.FilePath.Length > 0)
            {
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependencies.FilePath));
                if (CacheDependencies.ContainsKey(key))
                {
                    CacheDependencies.Remove(key);
                }
                CacheDependencies.Add(key, dependencies);
            }
            if (_cache.Add(item, policy))
            {
                if (OnRemoveCallbackDictionary.ContainsKey(key))
                {
                    if (OnRemoveCallbackDictionary.Remove(key))
                    {
                        OnRemoveCallbackDictionary.Add(key, onRemoveCallback);
                        return true;
                    }
                    _cache.Remove(key);
                    throw new Exception("注册缓存更新回调函数时出错，从指定字典中移除已存在项时失败!");
                }
                OnRemoveCallbackDictionary.Add(key, onRemoveCallback);
                return true;
            }
            return false;
        }
        #endregion

        #region 带有更新回调函数的插入缓存方法
        /// <summary>
        /// 带有更新回调函数的插入缓存方法
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="dependencies"></param>
        /// <param name="absoluteExpiration"></param>
        /// <param name="slidingExpiration"></param>
        /// <param name="onUpdateCallback"></param>
        /// <returns></returns>
        public bool Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemUpdateCallback onUpdateCallback)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "缓存的键值不能为空!");
            }
            if (absoluteExpiration == NoAbsoluteExpiration && slidingExpiration == NoSlidingExpiration)
            {
                throw new ArgumentException("absoluteExpiration和slidingExpiration不能全部为默认值!");
            }
            //如果回调函数为空,则抛出异常
            if (onUpdateCallback == null)
            {
                throw new ArgumentNullException("onUpdateCallback", "更新通知回调函数不能为 null");
            }
            if (_cache.Contains(key))
            {
                Remove(key);
            }
            CacheItem item = new CacheItem(key, value);
            System.Runtime.Caching.CacheItemPolicy policy = new System.Runtime.Caching.CacheItemPolicy
            {
                AbsoluteExpiration = absoluteExpiration == DateTime.MaxValue ? DateTimeOffset.MaxValue : absoluteExpiration,
                SlidingExpiration = slidingExpiration,
                RemovedCallback = RemovedCallback
            };
            //文件依赖
            if (dependencies != null && dependencies.FilePath != null && dependencies.FilePath.Length > 0)
            {
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependencies.FilePath));
                if (CacheDependencies.ContainsKey(key))
                {
                    CacheDependencies.Remove(key);
                }
                CacheDependencies.Add(key, dependencies);
            }

            if (_cache.Add(item, policy))
            {
                if (OnUpdateCallbackDictionary.ContainsKey(key))
                {
                    if (OnUpdateCallbackDictionary.Remove(key))
                    {
                        OnUpdateCallbackDictionary.Add(key, onUpdateCallback);
                        return true;
                    }
                    _cache.Remove(key);
                    throw new Exception("注册缓存更新回调函数时出错，从指定字典中移除已存在项时失败!");
                }
                OnUpdateCallbackDictionary.Add(key, onUpdateCallback);
                return true;
            }
            //throw new Exception("添加缓存到缓存器中时出错，换添加失败!");
            return false;
        }
        #endregion 

        #endregion

        #region 私有方法

        #region 当缓存移除时的回调委托
        /// <summary>
        /// 当缓存移除时的回调委托
        /// </summary>
        /// <param name="arguments"></param>
        private void RemovedCallback(CacheEntryRemovedArguments arguments)
        {
            //throw new Exception("ddd");
            if (OnRemoveCallbackDictionary.ContainsKey(arguments.CacheItem.Key))
            {
                #region 移除回调函数
                CacheItemRemovedCallback onRemoveCallback;
                OnRemoveCallbackDictionary.TryGetValue(arguments.CacheItem.Key, out onRemoveCallback);
                if (onRemoveCallback != null)
                {
                    CacheItemRemovedReason reason;
                    switch (arguments.RemovedReason)
                    {
                        case CacheEntryRemovedReason.Expired:
                            reason = CacheItemRemovedReason.Expired;
                            break;
                        case CacheEntryRemovedReason.Removed:
                            reason = CacheItemRemovedReason.Removed;
                            break;
                        case CacheEntryRemovedReason.Evicted:
                            reason = CacheItemRemovedReason.Underused;
                            break;
                        case CacheEntryRemovedReason.ChangeMonitorChanged:
                            reason = CacheItemRemovedReason.DependencyChanged;
                            break;
                        default:
                            reason = CacheItemRemovedReason.Expired;
                            break;
                    }
                    CacheDependency dependency = null;
                    //如果存在文件缓存依赖，则读取
                    if (CacheDependencies.ContainsKey(arguments.CacheItem.Key))
                    {
                        CacheDependencies.TryGetValue(arguments.CacheItem.Key, out dependency);
                        CacheDependencies.Remove(arguments.CacheItem.Key);
                    }
                    onRemoveCallback(arguments.CacheItem.Key, arguments.CacheItem.Value, reason, dependency);
                    OnRemoveCallbackDictionary.Remove(arguments.CacheItem.Key);
                }
                #endregion
            }
            if (OnUpdateCallbackDictionary.ContainsKey(arguments.CacheItem.Key))
            {
                #region 更新回调函数
                CacheItemUpdateCallback onUpdateCallback;
                OnUpdateCallbackDictionary.TryGetValue(arguments.CacheItem.Key, out onUpdateCallback);
                if (onUpdateCallback != null)
                {
                    CacheItemRemovedReason reason;
                    switch (arguments.RemovedReason)
                    {
                        case CacheEntryRemovedReason.Expired:
                            reason = CacheItemRemovedReason.Expired;
                            break;
                        case CacheEntryRemovedReason.Removed:
                            reason = CacheItemRemovedReason.Removed;
                            break;
                        case CacheEntryRemovedReason.Evicted:
                            reason = CacheItemRemovedReason.Underused;
                            break;
                        case CacheEntryRemovedReason.ChangeMonitorChanged:
                            reason = CacheItemRemovedReason.DependencyChanged;
                            break;
                        default:
                            reason = CacheItemRemovedReason.Expired;
                            break;
                    }
                    CacheDependency dependency = null;
                    //如果存在文件缓存依赖，则读取
                    if (CacheDependencies.ContainsKey(arguments.CacheItem.Key))
                    {
                        CacheDependencies.TryGetValue(arguments.CacheItem.Key, out dependency);
                        CacheDependencies.Remove(arguments.CacheItem.Key);
                    }
                    onUpdateCallback(arguments.CacheItem.Key, reason, arguments.CacheItem.Value, dependency);
                    OnUpdateCallbackDictionary.Remove(arguments.CacheItem.Key);
                }
                #endregion
            }
        }
        #endregion
        #endregion

        #region 移除指定键名的缓存项
        /// <summary>
        /// 移除指定键名的缓存项
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object Remove(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                return null;
            }
            if (_cache.Contains(key))
            {
                return _cache.Remove(key);
            }
            return null;
        }
        /// <summary>
        /// 移除缓存中所有缓存的数据
        /// </summary>
        public void RemoveAll()
        {
            foreach (var item in _cache.AsEnumerable())
            {
                //删除更新回调函数
                if (OnUpdateCallbackDictionary.ContainsKey(item.Key))
                {
                    OnUpdateCallbackDictionary.Remove(item.Key);
                }
                //删除移除回调函数
                if (OnRemoveCallbackDictionary.ContainsKey(item.Key))
                {
                    OnRemoveCallbackDictionary.Remove(item.Key);
                }
                //删除缓存依赖
                if (CacheDependencies.ContainsKey(item.Key))
                {
                    CacheDependencies.Remove(item.Key);
                }
                //移除缓存数据
                _cache.Remove(item.Key);
            }
        }
        #endregion 

        #region 获取值
        /// <summary>
        /// 获取指定键名的缓存值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object GetValue(string key)
        {
            return _cache.Get(key);
        }
        /// <summary>
        /// 获取指定键名的缓存值，并转换为指定的类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T GetValue<T>(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "获取缓存的键名不能为空!");
            }
            try
            {
                if (_cache.Contains(key))
                {
                    return (T)_cache.Get(key);
                }
                return default (T);
            }
            catch
            {
                return default(T);
            }
        }
        /// <summary>
        /// 获取指定键名的缓存项,如果缓存不存在则返回默认值
        /// </summary>
        /// <typeparam name="T">默认值的类型</typeparam>
        /// <param name="key">键名</param>
        /// <param name="defValue">返回的默认值</param>
        /// <returns></returns>
        public T GetValue<T>(string key, T defValue)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key", "获取缓存的键名不能为空!");
            }
            if (_cache.Contains(key))
            {
                try
                {
                    return (T) _cache.Get(key);
                }
                catch
                {
                    return defValue;
                }
            }
            return defValue;
        }
        /// <summary>
        /// 获取或添加指定的键名的键值
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public object GetOrAdd(string key, Func<object> func)
        {
            if (_cache.Contains(key))
            {
                return _cache.Get(key);
            }
            var obj = func();
            if (obj != null)
            {
                _cache.Add(new CacheItem(key, obj), null);
            }
            return obj;
        }
        #endregion

        #region 释放资源
		/// <summary>
        /// 析构函数
        /// </summary>
        ~SessionCache()
        {
            Dispose();
        }
        /// <summary>
        /// 释放 SessionCache 对象所占用的资源
        /// </summary>
        public void Dispose()
        {
            Dispose(true);//这样会释放所有的资源
            GC.SuppressFinalize(this);//不需要再调用本对象的Finalize方法

        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                //移除缓存的对象
                if (string.IsNullOrWhiteSpace(Name) == false && SessionCacheDictionary.ContainsKey(Name))
                {
                    SessionCacheDictionary.Remove(Name);
                }

                //移除缓存中的委托
                foreach (var item in _cache.AsEnumerable())
                {
                    if (string.IsNullOrWhiteSpace(item.Key) == false)
                    {
                        if (OnUpdateCallbackDictionary.ContainsKey(item.Key))
                        {
                            OnUpdateCallbackDictionary.Remove(item.Key);
                        }
                        if (OnRemoveCallbackDictionary.ContainsKey(item.Key))
                        {
                            OnRemoveCallbackDictionary.Remove(item.Key);
                        }
                    }
                }
                //是否缓存占用的资源
                if (_cache != null)
                {
                    _cache.Dispose();
                }                
            }
        } 
	    #endregion
    }
}
